from datetime import datetime, timedelta
import json
from typing import Optional, Annotated

import hashids
import httpx
from jose import JWTError, jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer

from application.http import schemas
from application.config import appSettings
from application.http import models
from sanren.extensions.redis import ExtRedis
from application.http.schemas import ApiSchemas
from sanren.utils.utils import Utils


# to get a string like this run:
# openssl rand -hex 32
oauth2_scheme = OAuth2PasswordBearer(tokenUrl=appSettings.token_url)

hashid = hashids.Hashids(salt=appSettings.secret_key, min_length=32)

# Redis
redis = ExtRedis().instance()


async def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    """ 用户验证成功，并且取出用户信息，然后据此创建jwt token """

    data_copy = data.copy()
    # 隐藏密码
    if expires_delta:
        expire = datetime.now() + timedelta(minutes=expires_delta)
    else:
        expire = datetime.now() + timedelta(minutes=appSettings.access_token_expire_minutes)
    # exp 是固定写法必须得传  sub和uid是自己存的值
    to_encode = {"exp": expire, "sub": str(data_copy['username']), "uid": hashid.encode(data_copy['id'])}
    encoded_jwt = jwt.encode(claims=to_encode, key=appSettings.secret_key, algorithm=appSettings.algorithm)
    return encoded_jwt


async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, appSettings.secret_key, algorithms=[appSettings.algorithm])
        username: str = payload.get("sub")
        user_id: str = payload.get("uid")
        expire_at: str = payload.get("exp")

        if username is None or user_id is None or expire_at < datetime.now().timestamp():
            raise credentials_exception
    except JWTError:
        raise credentials_exception

    # 查询用户是否存在
    hashid_user_id = hashid.decode(user_id)
    user_id = hashid_user_id[0] if hashid_user_id else 0
    if user_id == 0:
        raise credentials_exception

    # 判断是不是有登录过节点状态
    login_status = redis.get(f'node:{appSettings.node_code}:{username}')
    if login_status is None:
        raise credentials_exception

    # 获取用户信息并缓存
    user = await get_user(token)
    if user is None:
        raise credentials_exception

    return user


async def get_user(token):
    """ 验证用户信息 并 获取用户信息 """
    md5_token = Utils.md5(token)
    # 从缓存中获取用户信息
    user_info = redis.get(f'userinfo:{md5_token}')
    if user_info:
        return ApiSchemas.Profile(**json.loads(user_info))

    async with httpx.AsyncClient() as client:
        response = await client.get(f'{appSettings.zht_api_url}{appSettings.zht_api_token_verify_url}', headers={'Authorization': f'Bearer {token}'})

    if response.status_code == 200:
        res = response.json()
    else:
        return None

    return ApiSchemas.Profile(**res['data'])


async def get_current_active_user(
    current_user: Annotated[object, Depends(get_current_user)]
):
    if current_user.is_active == 0:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


__all__ = (
    create_access_token,
    get_current_user,
    get_current_active_user,
)
